﻿#region License

// Dimebrain TweetSharp
// (www.tweetsharp.com)
// 
// The MIT License
// 
// Copyright (c) 2009 Dimebrain
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 

#endregion

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Dimebrain.TweetSharp.Core.Extensions;
using Dimebrain.TweetSharp.Model;
using Dimebrain.TweetSharp.Model.Twitter;
using Dimebrain.TweetSharp.Model.Twitter.Converters;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

#if !Smartphone && !SILVERLIGHT

#endif

namespace Dimebrain.TweetSharp.Extensions
{
    public static partial class TwitterExtensions
    {
        private static readonly JsonSerializerSettings _settings =
            new JsonSerializerSettings
                {
                    MissingMemberHandling = MissingMemberHandling.Ignore,
                    NullValueHandling = NullValueHandling.Include,
                    DefaultValueHandling = DefaultValueHandling.Include,
                    Converters = (new JsonConverter[]
                                      {
                                          new TwitterDateTimeConverter(),
                                          new TwitterWonkyBooleanConverter(),
                                          new TwitterGeoLocationConverter()
                                      })
                };

        public static string ToTwitterResponseString(this string value)
        {
            if(value.IsNullOrBlank())
            {
                return string.Empty;
            }

            if (value.First() == '"')
            {
                value = value.Substring(1);
            }

            if (value.Last() == '"')
            {
                value = value.Substring(0, value.Length - 1);
            }

            return value;
        }

        public static string ToTwitterDateString(this DateTime date)
        {
            // Tue, 27 Mar 2007 22:55:48 GMT
            var result = date.ToString("ddd, dd MMM yyyy H':'mm':'ss 'GMT'".UrlEncode(), CultureInfo.InvariantCulture);
            return result;
        }

        /// <summary>
        /// This method attempts to cast an XML or JSON string into a <see cref="TwitterUser" /> collection.
        /// instance. If this method is not successful, null is returned.
        /// </summary>
        /// <param name="xmlOrJson">The XML or JSON input to convert</param>
        /// <returns>An user collection instance, or null if the input cannot cast to a user collection</returns>
        public static IEnumerable<TwitterUser> AsUsers(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif
            var collection = (JContainer) JsonConvert.DeserializeObject(xmlOrJson);
            if (collection == null)
            {
                return null;
            }

            var users = new List<TwitterUser>(0);
            foreach (var item in collection.Children())
            {
                try
                {
                    var user = item.ToString().Deserialize<TwitterUser>();
                    if (user == null)
                    {
                        return null;
                    }
                    users.Add(user);
                }
                catch (Exception)
                {
                    return null;
                }
            }

            return users;
        }

        /// <summary>
        /// This method attempts to cast an XML or JSON string into a <see cref="TwitterUser" />
        /// instance. If this method is not successful, null is returned.
        /// </summary>
        /// <param name="xmlOrJson">The XML or JSON input to convert</param>
        /// <returns>A user instance, or null if the input cannot cast to a user</returns>
        public static TwitterUser AsUser(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif

            var user = xmlOrJson.Deserialize<TwitterUser>();
            return user;
        }

        /// <summary>
        /// This method attempts to cast an XML or JSON string into a <see cref="TwitterStatus" /> collection
        /// instance. If this method is not successful, null is returned.
        /// </summary>
        /// <param name="xmlOrJson">The XML or JSON input to convert</param>
        /// <returns>A status collection instance, or null if the input cannot cast to a status collection</returns>
        public static IEnumerable<TwitterStatus> AsStatuses(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif
            try
            {
                var collection = (JContainer) JsonConvert.DeserializeObject(xmlOrJson);
                if (collection == null)
                {
                    return null;
                }

                var statuses = new List<TwitterStatus>();
                foreach (var item in collection.Children())
                {
                    try
                    {
                        var status = item.ToString().Deserialize<TwitterStatus>();
                        if (status == null)
                        {
                            return null;
                        }
                        statuses.Add(status);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error encountered trying to deserialize JSON: {0}", ex);
                        return null;
                    }
                }

                return statuses;
            }
            catch (Exception /*ex*/)
            {
                return null;
            }
        }

        public static TwitterStatus AsStatus(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif

            var status = xmlOrJson.Deserialize<TwitterStatus>();
            return status;
        }

        public static TwitterSavedSearch AsSavedSearch(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif

            var savedSearch = xmlOrJson.Deserialize<TwitterSavedSearch>();
            return savedSearch;
        }

        /// <summary>
        /// This method attempts to cast an XML or JSON string into a <see cref="TwitterSavedSearch" /> collection
        /// instance. If this method is not successful, null is returned.
        /// </summary>
        /// <param name="xmlOrJson">The XML or JSON input to convert</param>
        /// <returns>A saved search collection instance, or null if the input cannot cast to a saved search collection</returns>
        public static IEnumerable<TwitterSavedSearch> AsSavedSearches(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif
            try
            {
                var collection = (JContainer)JsonConvert.DeserializeObject(xmlOrJson);
                if (collection == null)
                {
                    return null;
                }

                var savedSearches = new List<TwitterSavedSearch>();
                foreach (var item in collection.Children())
                {
                    try
                    {
                        var savedSearch = item.ToString().Deserialize<TwitterSavedSearch>();
                        if (savedSearch == null)
                        {
                            return null;
                        }
                        savedSearches.Add(savedSearch);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error encountered trying to deserialize JSON: {0}", ex);
                        return null;
                    }
                }

                return savedSearches;
            }
            catch (Exception /*ex*/)
            {
                return null;
            }
        }


        public static IEnumerable<TwitterDirectMessage> AsDirectMessages(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif

            var collection = (JContainer) JsonConvert.DeserializeObject(xmlOrJson);
            if (collection == null)
            {
                return null;
            }

            var dms = new List<TwitterDirectMessage>();
            foreach (var item in collection.Children())
            {
                try
                {
                    var dm = item.ToString().Deserialize<TwitterDirectMessage>();
                    if (dm == null)
                    {
                        return null;
                    }
                    dms.Add(dm);
                }
                catch (Exception)
                {
                    return null;
                }
            }

            return dms;
        }

        public static TwitterDirectMessage AsDirectMessage(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif

            var dm = xmlOrJson.Deserialize<TwitterDirectMessage>();
            return dm;
        }

        public static TwitterRateLimitStatus AsRateLimitStatus(this string xmlOrJson)
        {
#if !SILVERLIGHT
            if (xmlOrJson.IsXml(out xmlOrJson))
            {
                return DeserializeRateLimitStatusXml(xmlOrJson);
            }
#endif

            var limit = xmlOrJson.Deserialize<TwitterRateLimitStatus>();
            return limit;
        }

        /// <summary>
        /// This method attempts to cast an XML or JSON string into a <see cref="TwitterError" />
        /// instance. If this method is not successful, null is returned.
        /// </summary>
        /// <param name="xmlOrJson">The XML or JSON input to convert</param>
        /// <returns>An error instance, or null if the input cannot cast to an error</returns>
        public static TwitterError AsError(this string xmlOrJson)
        {
#if !SILVERLIGHT
            if (xmlOrJson.IsXml(out xmlOrJson))
            {
                return DeserializeErrorXml(xmlOrJson);
            }
#endif

            var error = xmlOrJson.Deserialize<TwitterError>();
            return error;
        }

        /// <summary>
        /// This method attempts to cast an XML or JSON string into a <see cref="TwitterSearchResult" />
        /// instance. If this method is not successful, null is returned.
        /// </summary>
        /// <param name="xmlOrJson">The XML or JSON input to convert</param>
        /// <returns>A search result instance, or null if the input cannot cast to a search result</returns>
        public static TwitterSearchResult AsSearchResult(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif
            var result = xmlOrJson.Deserialize<TwitterSearchResult>();
            return result;
        }

        /// <summary>
        /// This method attempts to cast JSON string into a <see cref="TwitterSearchTrends" />
        /// instance. If this method is not successful, null is returned.
        /// </summary>
        /// <param name="xmlOrJson">The JSON input to convert</param>
        /// <returns>A search result instance, or null if the input cannot cast to a search result</returns>
        public static TwitterSearchTrends AsTrends(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif

            //FIX: This isn't pretty, but it gets this stuff working
            //The problem is that the json returned from the search api has a 
            //dynamically named property (a date/time) that we can't know ahead
            //of time. That property is also the parent of the trend objects, 
            //so we can't ignore it either.  This removes that property from the
            //json and reparents the trend objects to the "trends" property 
            //before passing it off to the deserializer to get turned into 
            //objects in our model. 
            JObject jobj = JObject.Parse( xmlOrJson );
            
            JProperty trendsProp = null;
            var props = from JProperty p
                          in jobj.Properties()
                          where p.Name.ToLower() == "trends"
                        select p; 
            if ( props.Count() > 0 )
            {
                trendsProp = props.ToArray()[0];
                var dynamicDateProp = trendsProp.Value;
                if ( dynamicDateProp.Type != JTokenType.Array )
                {
                    var first = dynamicDateProp.First.First;
                    trendsProp.Value = first;
                }
            }
            var sb = new StringBuilder();
            var stringWriter = new StringWriter(sb);
            var writer = new JsonTextWriter(stringWriter);
            jobj.WriteTo(writer);
            writer.Close();
            stringWriter.Dispose();
            xmlOrJson = sb.ToString();
            var trends = xmlOrJson.Deserialize<TwitterSearchTrends>();
            return trends;
        }

        /// <summary>
        /// This method attempts to cast an XML or JSON string into an arbitrary class instance. 
        /// If this method is not successful, null is returned.
        /// </summary>
        /// <param name="xmlOrJson">The XML or JSON input to convert</param>
        /// <returns>An T instance, or null if the input cannot cast to T</returns>
        public static T As<T>(this string xmlOrJson)
        {
#if !SILVERLIGHT
            xmlOrJson = PreProcessXml(xmlOrJson);
#endif

            var instance = JsonConvert.DeserializeObject<T>(xmlOrJson);
            return instance;
        }

        private static T Deserialize<T>(this string json) where T : class
        {
            try
            {
                var deserialized = JsonConvert.DeserializeObject<T>(json, _settings);
                return deserialized;
            }
            catch (JsonSerializationException jsEx)
            {
                return null;
            }
            catch (JsonReaderException jrEx)
            {
                // Issue 12: Twitter might return a plain string.
                return null;
            }
            catch (Exception ex)
            {
                // Collections have issues converting to singles
                return null;
            }
        }

        /// <summary>
        /// This method attempts to cast a string response into an <see cref="OAuthToken" />.
        /// If unsuccessful, null is returned.
        /// </summary>
        /// <param name="xmlOrJson">The XML or JSON input to convert</param>
        /// <returns>A token instance, or null if the input cannot cast to a token pair.</returns>
        public static OAuthToken AsToken(this string input)
        {
            return StringExtensions.AsToken(input);
        }
    }
}